package com.czg.jsonschema2lombokpojo.rules;

import static com.czg.jsonschema2lombokpojo.rules.PrimitiveTypes.*;
import static com.czg.jsonschema2lombokpojo.util.TypeUtil.*;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import com.czg.jsonschema2lombokpojo.config.GenerationConfig;
import com.czg.jsonschema2lombokpojo.Schema;

import com.fasterxml.jackson.databind.JsonNode;
import com.sun.codemodel.JClassContainer;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JType;

/**
 * 应用“type”schema规则。
 *
 */
public class TypeRule implements Rule<JClassContainer, JType> {

    private static final String DEFAULT_TYPE_NAME = "any";

    private final RuleFactory ruleFactory;

    protected TypeRule(RuleFactory ruleFactory) {
        this.ruleFactory = ruleFactory;
    }

    /**
     * 应用此schema规则执行所需的代码生成步骤。
     * <p>
     * 应用该规则时，将读取给定节点的详细信息，以确定要返回的适当Java类型。这可能是新生成的类型，
     * 它可以是一个基本类型或其他类型，如{@link java.lang。String}或{@link java.lang.Object}。
     * <p>
     * JSONschema类型和它们的Java类型等同：
     * <ul>
     * <li>"type":"any" =&gt; {@link java.lang.Object}
     * <li>"type":"array" =&gt; Either {@link java.util.Set} or {@link java.util.List}, 参见 {@link ArrayRule}
     * <li>"type":"boolean" =&gt; <code>boolean</code>
     * <li>"type":"integer" =&gt; <code>int</code>
     * <li>"type":"null" =&gt; {@link java.lang.Object}
     * <li>"type":"number" =&gt; <code>double</code>
     * <li>"type":"object" =&gt; 生成的类型（参见{@link ObjectRule}）
     * <li>"type":"string" =&gt;{@link java.lang.String}（或者根据是否存在“format”来选择，参见{@link FormatRule}）
     * </ul>
     *
     * @param nodeName        “type”规则应用的节点名称
     * @param node            这个“type”规则应用的节点
     * @param parent          父节点
     * @param jClassContainer 任何新生成的类型都可以放置在其中
     * @return Java类型，在读取给定schema节点的详细信息后，最合适地匹配指定的“type”
     */
    @Override
    public JType apply(String nodeName, JsonNode node, JsonNode parent, JClassContainer jClassContainer, Schema schema) {
        System.out.println(this.getClass().getName() + " apply field:" + nodeName);
        String propertyTypeName = getTypeName(node);
        JType type;
        if (propertyTypeName.equals("object") || node.has("properties") && node.path("properties").size() > 0) {
            type = ruleFactory.getObjectRule().apply(nodeName, node, parent, jClassContainer.getPackage(), schema);
        } else if (node.has("existingJavaType")) {
            String typeName = node.path("existingJavaType").asText();

            if (isPrimitive(typeName, jClassContainer.owner())) {
                type = primitiveType(typeName, jClassContainer.owner());
            } else {
                type = resolveType(jClassContainer, typeName);
            }
        } else if (propertyTypeName.equals("string")) {
            type = jClassContainer.owner().ref(String.class);
        } else if (propertyTypeName.equals("number")) {

            type = getNumberType(jClassContainer.owner(), ruleFactory.getGenerationConfig());
        } else if (propertyTypeName.equals("integer")) {

            type = getIntegerType(jClassContainer.owner(), node, ruleFactory.getGenerationConfig());
        } else if (propertyTypeName.equals("boolean")) {

            type = jClassContainer.owner().ref(Boolean.class);
        } else if (propertyTypeName.equals("array")) {

            type = ruleFactory.getArrayRule().apply(nodeName, node, parent, jClassContainer.getPackage(), schema);
        } else {

            type = jClassContainer.owner().ref(Object.class);
        }

        if (!node.has("javaType") && !node.has("existingJavaType") && node.has("format")) {
            type = ruleFactory.getFormatRule().apply(nodeName, node.get("format"), node, type, schema);
        } else if (!node.has("javaType") && !node.has("existingJavaType") && propertyTypeName.equals("string") && node.has("media")) {
            type = ruleFactory.getMediaRule().apply(nodeName, node.get("media"), node, type, schema);
        }
        return type;
    }

    private String getTypeName(JsonNode node) {
        if (node.has("type")) {
            final JsonNode typeNode = node.get("type");
            if (typeNode.isArray() && typeNode.size() > 0) {
                final List<String> typeValues = StreamSupport.stream(typeNode.spliterator(), false)
                        .map(JsonNode::asText)
                        .filter(n -> !"null".equals(n))
                        .collect(Collectors.toList());

                if (typeValues.size() == 1) {
                    return typeValues.get(0);
                }
            } else if (typeNode.isTextual()) {
                return typeNode.asText();
            }
        }

        return DEFAULT_TYPE_NAME;
    }


    /**
     * 返回一个整数字段的JType。处理类型查找和拆箱。
     */
    private JType getIntegerType(JCodeModel owner, JsonNode node, GenerationConfig config) {

        if (config.isUseBigIntegers()) {
            return owner.ref(BigInteger.class);
        } else if (config.isUseLongIntegers() || node.has("minimum") && node.get("minimum").isLong() || node.has("maximum") && node.get("maximum")
                .isLong()) {
            return owner.ref(Long.class);
        } else {
            return owner.ref(Integer.class);
        }

    }

    /**
     * 返回数字字段的JType。处理类型查找和拆箱。
     */
    private JType getNumberType(JCodeModel owner, GenerationConfig config) {

        if (config.isUseBigDecimals()) {
            return owner.ref(BigDecimal.class);
        } else if (config.isUseDoubleNumbers()) {
            return owner.ref(Double.class);
        } else {
            return owner.ref(Float.class);
        }

    }

}
